﻿using Digitalmes.Application.Mes;
using Digitalmes.Domain;
using Digitalmes.Domain.Enums;
using Digitalmes.Domain.Mes;
using Digitalmes.Scada.Models;
using Digitalmes.Scada.Utils;
using Digitalmes.Sugar;

namespace Digitalmes.Scada.Services.Impl;

/// <summary>
/// 产品出站服务
/// </summary>
internal sealed class ArchiveService : IArchiveService, ITransientDependency
{
    private readonly SugarRepository<ProdTransSn> _transSnRepo;
    private readonly SugarRepository<ProdTransSnRecord> _tranSnRecordRepo;
    private readonly SugarRepository<ProdInbound> _inboundRepo;
    private readonly SugarRepository<ProdArchive> _archiveRepo;
    private readonly SugarRepository<ProcProcessParams> _processParamsRepo;
    private readonly ProcessQueryService _processQuery;
    private readonly ProcBomCheckService _procBomCheckService;
    private readonly WoIncrementService _woIncrementService;
    private readonly ReworkRelaunchService _reworkRelaunchService;

    public ArchiveService(
        SugarRepository<ProdTransSn> transSnRepo,
        SugarRepository<ProdTransSnRecord> tranSnRecordRepo,
        SugarRepository<ProdInbound> inboundRepo,
        SugarRepository<ProdArchive> archiveRepo,
        SugarRepository<ProcProcessParams> processParamsRepo,
        ProcessQueryService processQuery,
        ProcBomCheckService procBomCheckService,
        WoIncrementService woIncrementService,
        ReworkRelaunchService reworkRelaunchService)
    {
        _transSnRepo = transSnRepo;
        _tranSnRecordRepo = tranSnRecordRepo;
        _inboundRepo = inboundRepo;
        _archiveRepo = archiveRepo;
        _processParamsRepo = processParamsRepo;
        _processQuery = processQuery;
        _procBomCheckService = procBomCheckService;
        _woIncrementService = woIncrementService;
        _reworkRelaunchService = reworkRelaunchService;
    }

    public async Task<HandleResult> ArchiveAsync(ArchiveInput input)
    {
        // 查看 SN 流转状态，是否当前正位于此工位（数据唯一）
        var transSn = await _transSnRepo.GetFirstAsync(s => s.Sn == input.SN);
        if (transSn is null || !(transSn.CurLineCode == input.LineCode && transSn.CurOpCode == input.OpCode))
        {
            return HandleResult.Error(ErrCode.E1204);
        }

        // 屏蔽重复出站动作（出站后 SN 流转状态会设置为 "出站"）
        if (transSn.Stage == SnStageEnum.Out)
        {
            return HandleResult.Error(ErrCode.E1205);
        }

        // -判断SN是否有进站信息（找到最新一条，可能存在重复进站）
        var inbound = await _inboundRepo.GetLastAsync(s => s.Sn == input.SN 
                && s.LineId == transSn.CurLineId
                && s.ProcId == transSn.CurProcId
                && s.OpId == transSn.CurOpId);
        if (inbound is null)
        {
            return HandleResult.Error(ErrCode.E1206);
        }

        // 检查是否为线外站，线外站没有工艺路线（线外站可以设置工序）
        bool isOutside = transSn.CurRoutingId == 0; // 是否为线外站
        if (isOutside)
        {
            // 线外站工作流，提前结束。
            return await OutsideArchiveAsync(transSn, inbound, input);
        }

        // -BOM 判断，校验此工位所上的关键物料与 BOM 是否匹配。
        var curProcCtl = await _processQuery.GetRoutingItemAsync(transSn.CurRoutingCode, transSn.CurProcCode); // 当前工序配置项
        if (curProcCtl!.PolicyCheckProcBom == PolicyCheckProcBomEnum.Over)
        {
            var (ok, _) = await _procBomCheckService.CheckArchiveAsync(transSn, curProcCtl.RoutingId, curProcCtl.ProcessId);
            if (!ok)
            {
                return HandleResult.Error(ErrCode.E1207);
            }
        }

        // -存储数据
        // --主数据
        ProdArchive archive = new()
        {
            LineId = transSn.CurLineId,
            LineCode = transSn.CurLineCode,
            OpId = transSn.CurOpId,
            OpCode = transSn.CurOpCode,
            ProcId = transSn.CurProcId,
            ProcCode = transSn.CurProcCode,
            Sn = transSn.Sn,
            ProductCode = transSn.ProductCode,
            Wo = transSn.Wo,
            PassResult = input.PassResult,
            FormualNo = input.FormualNo,
            Operator = input.Operator ?? string.Empty,
            Shift = input.Shift,
            Rfid = input.Rfid,
            CycleTime = CalCycleTime(input.CycleTime, inbound.InboundTime),
            ArchiveTime = DateTime.Now,
            Version = inbound.Version,
        };

        // --过程数据
        var (archiveDataLine, ngSteps) = await BuildArchiveDataAsync(transSn, input.AttachData);
        ProdArchiveData archiveData = new()
        {
            TypeCode = curProcCtl.Process.ProcTypeCode,
            TypeName = curProcCtl.Process.ProcTypeName,
            Data = archiveDataLine,
            Archive = archive,
        };

        // 一对一导航插入（SqlSugar 中怪异语法）
        await _archiveRepo.Context
            .InsertNav(archiveData)
            .Include(s => s.Archive)
            .ExecuteCommandAsync();

        // -修改 SN 流转状态
        bool isFinal = await _processQuery.IsFinalAsync(transSn.CurRoutingCode, transSn.CurProcCode);

        transSn.OutboundTime = archive.ArchiveTime;
        transSn.Stage = SnStageEnum.Out;
        transSn.Status = archive.PassResult switch 
        { 
            PassResultEnum.OK or PassResultEnum.MandatoryOK => SnStatusEnum.OK,
            PassResultEnum.NG or PassResultEnum.MandatoryNG => SnStatusEnum.NG,
            _ => SnStatusEnum.OK,
        };
        transSn.NgSteps = ngSteps; // NG 步序集合
        transSn.IsCompelted = isFinal && transSn.Status == SnStatusEnum.OK; // 完工判断（最后工序，且 OK 下线）

        if (transSn.IsCompelted) // 若是已完工
        {
            transSn.OfflineTime = archive.ArchiveTime;
        }

        // 在线返工过站处理
        // 此处用于使用 transSn 对象，需注意方法调用的位置。
        var (isReworkProc, isReworkDone) = (false, false);
        if (transSn.IsNeedRework())
        {
            (isReworkProc, isReworkDone) = await _reworkRelaunchService.OnlineReworkAsync(transSn, archive.Operator);

            // 是返工工位且返工未结束时，更新为 "返工中" 状态，其他情况重置。
            transSn.ReworkFlag = isReworkProc && !isReworkDone ? ReworkFlagEnum.Working : ReworkFlagEnum.None;
        }

        await _transSnRepo.UpdateAsync(transSn, s => new
        {
            s.OutboundTime,
            s.Stage,
            s.Status,
            s.IsCompelted,
            s.OfflineTime,
            s.ReworkFlag,
            s.NgSteps,
        });

        // -添加过站记录
        ProdTransSnRecord transRecord = new()
        {
            Sn = transSn.Sn,
            ProductId = transSn.ProductId,
            ProductCode = archive.ProductCode,
            Wo = archive.Wo,
            LineId = archive.LineId,
            LineCode = archive.LineCode,
            OpId = archive.OpId,
            OpCode = archive.OpCode,
            ProcId = archive.ProcId,
            ProcCode = archive.ProcCode,
            RoutingId = transSn.CurRoutingId,
            RoutingCode = transSn.CurRoutingCode,
            InboundTime = inbound.InboundTime,
            OutboundTime = archive.ArchiveTime,
            CycleTime = archive.CycleTime,
            StdWorkDuration = curProcCtl.StdOpDuration + curProcCtl.StdPrepDuration,
            Shift = archive.Shift,
            Operator = archive.Operator,
            PassResult = archive.PassResult,
            NgSteps = transSn.NgSteps,
            FlowType = isReworkProc ? FlowTypeEnum.Rework : FlowTypeEnum.Normal,
            ReworkDoc = isReworkProc ? transSn.ReworkDoc : null,
            CustomerSn = transSn.CustomerSn,
            Version = inbound.Version,
        };

        await _tranSnRecordRepo.InsertAsync(transRecord);

        // -修改单据状态（若存在单据）
        // 确保 transSn 数据有新增或更新后再处理工单信息。
        if (!string.IsNullOrEmpty(transSn.Wo))
        {
            // 在当前为返工工位且返工结束时，以返工形式更新工单
            if (isReworkProc && isReworkDone)
            {
                // 返工作业更新工单
                await _woIncrementService.OnlineReworkIncrementAsync(transSn.Wo, transSn.Status, transSn.IsCompelted);
            }
            else
            {
                // 正常作业更新工单（OK且完工时才会去更新）
                await _woIncrementService.OnlineIncrementWithFullAsync(transSn.Wo, transSn.Status, transSn.IsCompelted);
            }
        }

        return HandleResult.Ok(transSn);
    }

    /// <summary>
    /// 线外站数据存档。
    /// </summary>
    /// <returns></returns>
    private async Task<HandleResult> OutsideArchiveAsync(ProdTransSn transSn, ProdInbound inbound, ArchiveInput input)
    {
        // -存储数据
        // --主数据
        ProdArchive archive = new()
        {
            LineId = transSn.CurLineId,
            LineCode = transSn.CurLineCode,
            OpId = transSn.CurOpId,
            OpCode = transSn.CurOpCode,
            ProcId = transSn.CurProcId,
            ProcCode = transSn.CurProcCode,
            Sn = transSn.Sn,
            ProductCode = transSn.ProductCode,
            Wo = transSn.Wo,
            PassResult = input.PassResult,
            FormualNo = input.FormualNo,
            Operator = input.Operator,
            Shift = input.Shift,
            Rfid = input.Rfid,
            CycleTime = CalCycleTime(input.CycleTime, inbound.InboundTime),
            ArchiveTime = DateTime.Now,
            Version = inbound.Version,
        };

        // TODO: 线外站如果定义工艺类型
        var (archiveDataLine, ngSteps) = await BuildArchiveDataAsync(transSn, input.AttachData);
        ProdArchiveData archiveData = new()
        {
            TypeCode = "",
            TypeName = "",
            Data = archiveDataLine,
            Archive = archive,
        };

        // 一对一导航插入（SqlSugar 中怪异语法）
        await _archiveRepo.Context
            .InsertNav(archiveData)
            .Include(s => s.Archive)
            .ExecuteCommandAsync();

        transSn.OutboundTime = archive.ArchiveTime;
        transSn.Stage = SnStageEnum.Out;
        transSn.Status = archive.PassResult switch
        {
            PassResultEnum.OK or PassResultEnum.MandatoryOK => SnStatusEnum.OK,
            PassResultEnum.NG or PassResultEnum.MandatoryNG => SnStatusEnum.NG,
            _ => SnStatusEnum.OK,
        };
        transSn.IsCompelted = transSn.Status == SnStatusEnum.OK; // 完工判断（最后工序，且 OK 下线）
        if (transSn.IsCompelted) // 若是已完工
        {
            transSn.OfflineTime = archive.ArchiveTime;
        }
        await _transSnRepo.UpdateAsync(transSn, s => new { s.OutboundTime, s.Stage, s.Status, s.IsCompelted, s.OfflineTime });

        // -添加过站记录
        ProdTransSnRecord transRecord = new()
        {
            Sn = input.SN,
            ProductId = transSn.ProductId,
            ProductCode = archive.ProductCode,
            Wo = archive.Wo,
            LineId = archive.LineId,
            LineCode = archive.LineCode,
            OpId = archive.OpId,
            OpCode = archive.OpCode,
            ProcId = archive.ProcId,
            ProcCode = archive.ProcCode,
            RoutingId = transSn.CurRoutingId, // 线外站，为 0
            RoutingCode = string.Empty, // 线外站
            InboundTime = inbound.InboundTime,
            OutboundTime = archive.ArchiveTime,
            CycleTime = archive.CycleTime,
            StdWorkDuration = 0, // 线外站没有设置工序，暂不计算
            Shift = archive.Shift,
            Operator = archive.Operator,
            PassResult = archive.PassResult,
            NgSteps = transSn.NgSteps,
            Version = inbound.Version,
        };

        await _tranSnRecordRepo.InsertAsync(transRecord);

        return HandleResult.Ok();
    }

    /// <summary>
    /// 构建存档过程数据
    /// </summary>
    private async Task<(List<ArchiveDataItem> data, List<int> ngSteps)> BuildArchiveDataAsync(ProdTransSn transSn, IReadOnlyList<PayloadData>? dataList)
    {
        if (dataList is null)
        {
            return ([], []);
        }

        // 找到对应的工艺参数
        var procParams = await _processParamsRepo.GetListAsync(s => s.ProcessId == transSn.CurProcId && s.ProductId == transSn.ProductId);

        List<ArchiveDataItem> items = [];
        List<int> ngSteps = [];

        foreach (IGrouping<string, PayloadData> group in dataList.GroupBy(s => s.Group))
        {
            // 未分组数据
            if (string.IsNullOrEmpty(group.Key))
            {
                foreach (var data in group)
                {
                    var param = procParams?.FirstOrDefault(s => s.TagName == data.TagName && s.Step == 0);
                    ArchiveDataItem item = new()
                    {
                        I = 0,
                        K = data.DisplayName,
                        V = data.GetString(),
                        D = param?.StdLower,
                        H = param?.StdHigher,
                        R = "",
                    };
                    items.Add(item);
                }
            }
            else
            {
                // 目前分组值都是数组类型。
                // 分组为 "编号"、"值" 和 "结果" 三类，其中 "编号" 一定存在且唯一。
                var noData = group.FirstOrDefault(s => s.ValueUsage == TagValueUsage.No); // short 类型
                if (noData is null)
                {
                    return ([], []);
                }

                var valueDatas = group.Where(s => s.ValueUsage == TagValueUsage.Numerical).ToArray(); // float 或 int 类型
                var resultData = group.FirstOrDefault(s => s.ValueUsage == TagValueUsage.Result); // short 类型

                var noArray = noData.GetIntArray();
                var resultArr = resultData?.GetIntArray();
                for (var i = 0; i < noArray!.Length; i++)
                {
                    // 跳过数组中编号为 0 的项。
                    int no = noArray[i];
                    if (no == 0)
                    {
                        continue;
                    }

                    string r = resultArr != null
                            ? resultArr[i] switch
                            {
                                1 => "OK",
                                2 => "NG",
                                _ => "", // 还未处理
                            }
                            : string.Empty;

                    // 记录 NG 的步序
                    if (r == "NG")
                    {
                        ngSteps.Add(no);
                    }

                    foreach (var valueData in valueDatas)
                    {
                        var valueArr = valueData.GetStringArray();

                        string k = valueData.DisplayName;
                        string v = valueArr != null ? valueArr[i] : string.Empty;
                        var param = procParams?.FirstOrDefault(s => s.TagName == valueData.TagName && s.Step == no);

                        ArchiveDataItem item = new()
                        {
                            I = no,
                            G = group.Key,
                            K = k,
                            V = v,
                            D = param?.StdLower,
                            H = param?.StdHigher,
                            R = r,
                        };
                        items.Add(item);
                    }
                }
            }
        }

        return (items, ngSteps);
    }

    /// <summary>
    /// 计算 CycleTime 时长
    /// </summary>
    /// <param name="cycleTime"></param>
    /// <param name="inboundTime"></param>
    /// <returns></returns>
    private static double CalCycleTime(double? cycleTime, DateTime inboundTime)
    {
        double? ct = cycleTime > 0.1 ? cycleTime : (DateTime.Now - inboundTime).TotalSeconds; // 0.1 是针对浮点型比较
        return NumberUtil.Fix(ct ?? 0);
    }
}
